package com.nado.extensions.util;

import org.apache.commons.collections.MapUtils;
import org.apache.xml.serialize.OutputFormat;
import static org.apache.xml.serialize.OutputFormat.Defaults;
import org.w3c.dom.Document;

import java.io.OutputStream;
import java.io.Writer;
import java.util.Map;

/**
 * XML Document 객체를 OutputStream으로 직렬화하는 기능을 제공하는 클래스
 */
public class XMLSerializer {
	private Object output;
	private org.apache.xml.serialize.XMLSerializer serializer = new org.apache.xml.serialize.XMLSerializer();
	
	public static XMLSerializer create() {
		return new XMLSerializer();
	}

	public static XMLSerializer create(Object output) {
		return create(output, null);
	}

	public static XMLSerializer create(Object output, Map<String, Object> properties) {
		XMLSerializer serializer = new XMLSerializer();
		serializer.setOutputStream(output);
		if (properties != null) {
			serializer.configure(properties);
		}
		return serializer;
	}

	private XMLSerializer() {}

	public void setOutputStream(Object output) {
		if (output instanceof OutputStream) {
			serializer.setOutputByteStream((OutputStream) output);
		} else if (output instanceof Writer) {
			serializer.setOutputCharStream((Writer) output);
		} else {
			throw new IllegalArgumentException("not supported output " + output);
		}
		this.output = output;
	}

	public void configure(Map<String, Object> properties) {
		OutputFormat outputformat = new OutputFormat();

		outputformat.setEncoding(MapUtils.getString(properties, "encoding", Defaults.Encoding));

		// should keep order
		outputformat.setIndenting(MapUtils.getBoolean(properties, "indenting", true));
		outputformat.setIndent(MapUtils.getInteger(properties, "indent", Defaults.Indent));

		outputformat.setPreserveSpace(MapUtils.getBoolean(properties, "preserve-space", false));
		outputformat.setPreserveEmptyAttributes(MapUtils.getBoolean(properties, "preserve-empty-attr", false));

		outputformat.setLineWidth(MapUtils.getInteger(properties, "line-width", Defaults.LineWidth));
		outputformat.setLineSeparator(MapUtils.getString(properties, "line-separator", null));

		outputformat.setOmitComments(MapUtils.getBoolean(properties, "omit-comments", false));
		outputformat.setOmitDocumentType(MapUtils.getBoolean(properties, "omit-document-type", false));

		serializer.setOutputFormat(outputformat);
	}

	public void serialize(Document document) {
		try {
			serializer.serialize(document.getDocumentElement());
		} catch (Exception e) {
			String message = "failed to serialize " + document.toString() + " by " + e.toString();
			try {
				if (output instanceof OutputStream) {
					((OutputStream) output).write(message.getBytes());
				} else if (output instanceof Writer) {
					((Writer) output).write(message);
				}
			} catch (Exception t) {
				// ignore
			}
		}
	}
}
